package timbap.visualizer.coreplugins;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import processing.core.PImage;
import timbap.visualizer.api.context.PluginContext;
import timbap.visualizer.api.model.PlaylistEntry;
import timbap.visualizer.api.model.ArtworkData;
import timbap.visualizer.api.plugin.poscloud.AbstractCacheablePosCloudPlugin;
import timbap.visualizer.api.plugin.poscloud.cache.AbstractPosCloudMapXmlAdapter;
import timbap.visualizer.coreplugins.lib.artworkhue.ArtworkHuePosCloudMapXmlAdapter;
import timbap.visualizer.coreplugins.lib.artworkhue.HueCluster;
import timbap.visualizer.coreplugins.lib.artworkhue.HueComparableColor;
import timbap.visualizer.coreplugins.lib.artworkhue.PlaylistEntryByHueComparator;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.awt.*;

public class ArtworkHuePosCloudPlugin extends AbstractCacheablePosCloudPlugin<HueComparableColor> {

    private static final Log LOG = LogFactory.getLog(ArtworkHuePosCloudPlugin.class);
    private static final String TEMP_ARTWORK_HUES_CACHE_FILE_PATH = "temp/artwork_hues_cache.xml";


    private static final int HUE_QUANTIZATION_LEVELS = 100;
    private static final float HUE_RELEVANCE_THRESHOLD = 0.02f;
    
    private Comparator<PlaylistEntry> playlistEntryByHueComparator;
    private AbstractPosCloudMapXmlAdapter<HueComparableColor> posCloudMapXmlAdapter;

    @Override
    public void init(PluginContext pPluginContext) {

        posCloudMapXmlAdapter =
                new ArtworkHuePosCloudMapXmlAdapter(
                        pPluginContext.getPlaylistManager(),
                        pPluginContext.getTextureCacheManager());

        super.init(pPluginContext);

        playlistEntryByHueComparator =
                new PlaylistEntryByHueComparator(
                        getPosCloudElementMap());

        setElementDisplayCentered(false);
        setElementWidthFixed(true);
    }

    public boolean isCachedPosCloudElementValid(PlaylistEntry pPlaylistEntry,
                                             HueComparableColor pHueComparableColor) {

        ArtworkData tArtworkData = getContext().getTextureCacheManager().getArtworkData(pPlaylistEntry);
        Long tSavedChecksum = pHueComparableColor.getArtworkDataChecksum();
        Long tActualChecksum = tArtworkData.getChecksum();
        return (tSavedChecksum != null && tActualChecksum != null
                    && tSavedChecksum.equals(tActualChecksum));
    }

    public File getCacheFile() {
        return new File(TEMP_ARTWORK_HUES_CACHE_FILE_PATH);
    }

    public Comparator<PlaylistEntry> getPlaylistEntryComparator() {
        return playlistEntryByHueComparator;
    }

    public AbstractPosCloudMapXmlAdapter<HueComparableColor> getPosCloudMapXmlAdapter() {
        return posCloudMapXmlAdapter;
    }

    @Override
    public void drawPosCloudElement(HueComparableColor pPosCloudElement, boolean pIsSelected) {
    	if (pIsSelected) {
    		getContext().getP5Applet().translate(-5, 0);
    		getContext().getP5Applet().scale(2f,1);
    	}
        getContext().getP5Applet().fill(pPosCloudElement.getRed(),
                                        pPosCloudElement.getGreen(),
                                        pPosCloudElement.getBlue());
        getContext().getP5Applet().rect(0, 0, 10, 40);
    }

    @Override
    public HueComparableColor createPosCloudElement(PlaylistEntry pPlaylistEntry) {
        return getPredominantArtworkColor(pPlaylistEntry);
    }

    private HueComparableColor getPredominantArtworkColor(PlaylistEntry pPlaylistEntry) {

        int tEncodedColor = Color.GRAY.getRGB();
        
        PImage tArtworkTexture = getContext().getTextureCacheManager().getArtworkTextureInSync(pPlaylistEntry);
        int[] tPixels = tArtworkTexture.pixels;
        HashMap<Integer,Integer> hueOccurrences = findHueOccurrences(tPixels);
        
        List<HueCluster> tHueClusters = findHueClusters(hueOccurrences, tPixels.length);
        Collections.sort(tHueClusters);
        
        HueCluster tMostRelevantHueCluster = tHueClusters.get(tHueClusters.size() - 1);
        if (LOG.isDebugEnabled()) {
            LOG.debug("found most relevant hue cluster: " + tMostRelevantHueCluster);
        }
        
        float tMedianHue = tMostRelevantHueCluster.getMedianHue();
        if (LOG.isDebugEnabled()) {
            LOG.debug("has median hue: " + tMedianHue);
        }
        if (tMedianHue != -1) {
            tEncodedColor = Color.HSBtoRGB(tMedianHue, 0.5f, 0.5f);
        }

        ArtworkData tArtworkData = getContext().getTextureCacheManager().getArtworkData(pPlaylistEntry);
        return new HueComparableColor(new Color(tEncodedColor), tArtworkData.getChecksum());
    }
    
    private boolean isPixelColored(int pPixel) {
        float tPixelSaturation = getContext().getP5Applet().saturation(pPixel);
        float tPixelBrightness = getContext().getP5Applet().brightness(pPixel);
        return (tPixelSaturation > 255 * HueComparableColor.SATURATION_LOWER_THRESHOLD
                && tPixelBrightness > 255 * HueComparableColor.BRIGHTNESS_LOWER_THRESHOLD
                && tPixelBrightness < 255 * HueComparableColor.BRIGHTNESS_UPPER_THRESHOLD);
    }
    
    private HashMap<Integer, Integer> findHueOccurrences(int[] pPixels) {
        
        HashMap<Integer,Integer> tHueOccurrences = new HashMap<Integer, Integer>();
        for (int tCurrentPixel : pPixels) {
            if (isPixelColored(tCurrentPixel)) {
                float tPixelHue = getContext().getP5Applet().hue(tCurrentPixel);
                int tQuantizedPixelHue = (int) (tPixelHue / 255 * HUE_QUANTIZATION_LEVELS);
                Integer tCurrentCountOfThisHue = tHueOccurrences.get(tQuantizedPixelHue);
                if (tCurrentCountOfThisHue == null) {
                    tCurrentCountOfThisHue = 0;
                }
                tHueOccurrences.put(tQuantizedPixelHue, tCurrentCountOfThisHue + 1);
            }
        }
        return tHueOccurrences;
    }

    public List<HueCluster> findHueClusters(HashMap<Integer,Integer> pHueOccurrences, int pPixelCount) {

        int tMaxElems = (int) (HUE_QUANTIZATION_LEVELS * HUE_RELEVANCE_THRESHOLD); 
        List<HueCluster> tHueClusters = new ArrayList<HueCluster>(tMaxElems);
        
        HueCluster tCurrentHueCluster = new HueCluster();
        for (int i=0; i < HUE_QUANTIZATION_LEVELS; i++) {
            Integer tHueQuantCount = pHueOccurrences.get(i);
            if (tHueQuantCount != null && (tHueQuantCount > pPixelCount * HUE_RELEVANCE_THRESHOLD)) {
                tCurrentHueCluster.addHueValue((float)i / (float)HUE_QUANTIZATION_LEVELS,
                                               tHueQuantCount.floatValue() / (float)pPixelCount);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("added hue quant " + i + " (" + (tHueQuantCount / (float)pPixelCount) + ")");
                }
            } else {
                tHueClusters.add(tCurrentHueCluster);
                tCurrentHueCluster = new HueCluster();
            }
        }
        
        return tHueClusters;
    }

}
